/*
 * Copyright (C) 2000-2005 Philips Electronics N.V.
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or 
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file gnu_license.txt.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  
 * 
 */
#include <string.h>
#include <stddef.h>
#include <ctype.h>
#include <front/text.h>
#include <front/mem.h>
#include "cfront.h"

static char *escape_chars="abfnrtv\'\"\?\\";
static char *escaped_chars="\a\b\f\n\r\t\v\'\"\?\\";

typedef struct string {
  int   len;
  char *string;
} *string;

typedef struct wstring {
  int      len;
  wchar_t *string;
} *wstring;

/* ----------------------------------------------------------------------------
 * Append C to the string S
 */
string
append_char(string s, char c)
{
  if(!s) {
    s=(string)fe_malloc(sizeof(struct string));
    s->len=0;
    s->string=NULL;
  }
  s->len++;
  s->string = fe_realloc(s->string, s->len);
  s->string[s->len-1]=c;
  return s;
}

/* ----------------------------------------------------------------------------
 * Append C to the string S
 */
wstring
append_wchar(wstring s, wchar_t c)
{
  if(!s) {
    s=(wstring)fe_malloc(sizeof(struct wstring));
    s->len=0;
    s->string=NULL;
  }
  s->len++;
  s->string = fe_realloc(s->string, sizeof(wchar_t)*s->len);
  s->string[s->len-1]=c;
  return s;
}

/* ----------------------------------------------------------------------------
 * TRUE if S is an ASCII octal digit
 */
static Bool
isodigit(char s)
{
  return '0'<=s && s<='7';
}

/* ----------------------------------------------------------------------------
 * TRUE if S is one of the characters that can be escaped
 */
static Bool
is_escape(char s)
{
  return strchr(escape_chars,s)!=NULL;
}

/* ----------------------------------------------------------------------------
 * TRUE if S is one of the characters that can be escaped
 */
static Bool
escapable(char s)
{
  return s && strchr(escaped_chars,s)!=NULL;
}

/* ----------------------------------------------------------------------------
 * 'S' -> '\S'
 */
static char
add_escape(char s)
{
  char *p = strchr(escape_chars,s);
  if(!p) return s;
  return escaped_chars[p-escape_chars];
}

/* ----------------------------------------------------------------------------
 * '\S' -> 'S'
 */
static char
no_escape(char s)
{
  char *p = strchr(escaped_chars,s);
  if(!p) return s;
  return escape_chars[p-escaped_chars];
}

/* ----------------------------------------------------------------------------
 * Get the octal value in *P,  afterward P points to the last character of the
 * value
 */
static unsigned int
oct_value(char **p, Bool *ok)
{
  int k;
  unsigned int v=0;
  char *s=*p;
  
  for(k=0; k<3 && isodigit(*s); k++) {
    v = (v<<3) | (*s-'0');
    s++;
  }
  if(k==0) *ok = FALSE;
  *p=s-1;
  return v;
}

/* ----------------------------------------------------------------------------
 * Get the hexadecimal value in *P+1, (*p should be 'x')
 * afterward P points to the last character of the value
 */
#ifdef hex_value
#undef hex_value
#endif
static unsigned int
hex_value(char **p, Bool *ok)
{
  char *s=*p;
  unsigned int v;

  v=0;
  if(!isxdigit((int)s[1])) {
    *ok = FALSE;
    return v;
  }
  do {
    s++;
    if(isdigit((int)*s))        v = (v<<4) | (*s-'0');
    else if(isxdigit((int)*s))  v = (v<<4) | (10+tolower(*s)-'a');
  } while(isxdigit((int)s[1]));
  *p=s;
  return v;
}

/* ----------------------------------------------------------------------------
 * Concatenate the strings in STRINGS and remove escape sequences
 */
static string
no_escapes(List_String strings)
{
  Bool escape=FALSE, ok=TRUE;
  String s;
  string result=NULL;
  
  for(;strings && ok;
      strings = List_String_next(strings)) {
    s = List_String_data(strings);
    if(*s=='L') return NULL;
    s++;
    while(s[1] && ok) {
      if(!escape && *s=='\\') {
        escape=TRUE;
      } else if(escape) {
        escape=FALSE;
        if(is_escape(*s))     result = append_char(result, add_escape(*s));
        else if(*s=='x')      result = append_char(result, hex_value(&s,&ok));
        else if(isodigit(*s)) result = append_char(result, oct_value(&s,&ok));
        else ok=FALSE;
      } else {
        result = append_char(result, *s);
      }
      s++;
    }
  }
  if(!ok) return NULL;
  return append_char(result, 0);
}

/* ----------------------------------------------------------------------------
 * Concatenate the strings in STRINGS and remove escape sequences
 */
static string
wide_no_escapes(List_String strings)
{
  Bool escape=FALSE, ok=TRUE;
  String s;
  string result=NULL;
  
  for(; strings && ok;
      strings = List_String_next(strings)) {
    s = List_String_data(strings);
    if(*s=='L') s++;
    s++;
    while(s[1] && ok) {
      if(!escape && *s=='\\') {
        escape=TRUE;
      } else if(escape) {
        escape=FALSE;
        if(is_escape(*s))     result = append_char(result, add_escape(*s));
        else if(*s=='x')      result = append_char(result, hex_value(&s,&ok));
        else if(isodigit(*s)) result = append_char(result, oct_value(&s,&ok));
        else ok=FALSE;
      } else {
        result = append_char(result, *s);
      }
      s++;
    }
  }
  if(!ok) return NULL;
  /* add a NUL, convenient for printing */
  result = append_char(result, 0);
  result->len--;
  return result;
}

/* ----------------------------------------------------------------------------
 * Concatenate the StringExpr EXPR and remove escape sequences
 */
void
TranslateStrings(expression expr)
{
  List_String strings = StringExpr_s(expr);
  
  if (expression_tag(expr)!=StringExpr_kind || StringExpr_is_translated(expr)) {
    return;
  }
  StringExpr_is_translated(expr) = TRUE;

  if(List_String_data(strings)[0]=='L') {
    string result = wide_no_escapes(strings);
    strings = Create_List_String(result->string, NULL);
    StringExpr_s(expr)    = strings;
    StringExpr_size(expr) = result->len;
    StringExpr_wide(expr) = TRUE;
    free (result);
  } else {
    string result = no_escapes(strings);
    strings = Create_List_String(result->string, NULL);
    StringExpr_s(expr)     = strings;
    StringExpr_size(expr)  = result->len;
    free (result);
  }
}

static void
translate_CharExpr (CharExpr e)
{
  List_String strings;

  if (CharExpr_is_translated(e)) return;
  
  CharExpr_is_translated(e) = TRUE;
  strings = Create_List_String (CharExpr_c(e), NULL);
  if (CharExpr_c(e)[0]=='L') {
    string result = wide_no_escapes(strings);
    expression_tag (e) = WideCharExpr_kind;
    WideCharExpr_c(e) = (char*)result->string;
    WideCharExpr_size(e) = result->len;
    WideCharExpr_val(e) = NULL;
    free (result);
  } else {
    string result = no_escapes(strings);
    CharExpr_c(e) = result->string;
    free (result);
  }
}

/* ----------------------------------------------------------------------------
 * reverse the hex pattern in V
 */
static unsigned
hex_rev(unsigned v)
{
  unsigned i, r=0;
  
  for(i=UINT_MAX; i!=0; i>>=4) {
    r=(r<<4) | (v&0xF);
    v>>=4;
  }
  return r;
}

/* ----------------------------------------------------------------------------
 * Append the ascii hex value of V to S
 */
static void
print_hex(unsigned v)
{
  unsigned init=0;
  unsigned j;
  
  v=hex_rev(v);
  for(j=UINT_MAX; j!=0; j>>=4, v>>=4) {
    init+=v&0xF;
    if (init==0) continue;
    if((v&0xF)>=0xA)  Front_outputf("%c",((v-0xA)&0xF)+'A');
    else              Front_outputf("%c",(v&0xF)+'0');
  }
  if (init==0) Front_outputf("0");
}

/* ----------------------------------------------------------------------------
 * print string s and add escape sequences
 */
static void
print_quoted_string(char *s, int len, char *quote)
{
  int i;

  Front_outputf(quote);
  for(i=0;i<len-1; i++) {
    if(isprint((int)s[i]) && !escapable(s[i])) {
      Front_outputf("%c",s[i]);
    } else {
      Front_outputf("\\");
      if(escapable(s[i]))  Front_outputf("%c",no_escape(s[i]));
      else {
        Front_outputf("x");
        print_hex((unsigned char)s[i]);
        if(isxdigit((int)s[i+1])) {
          Front_outputf("%s%s",quote,quote);
        }
      }
    }
  }
  Front_outputf(quote);
}

/* ----------------------------------------------------------------------------
 * add escape sequences to the string S of length LEN
 */
static void
print_wide_quoted_string(char *s, int len, char *quote)
{
  int i;
  
  Front_outputf("L%s",quote);
  for(i=0;i<len-1; i++) {
    if(isprint((int)s[i]) && !escapable(s[i])) {
      Front_outputf("%c",s[i]);
    } else {
      Front_outputf("\\");
      if(escapable(s[i]))  Front_outputf("%c", no_escape(s[i]));
      else {
        Front_outputf("x");
        print_hex(s[i]);
        if(isxdigit(s[i+1])) {
          Front_outputf("%sL%s",quote,quote);
        }
      }
    }
  }
  Front_outputf(quote);
}

/* ----------------------------------------------------------------------------
 * Copy StringExpr expr
 */
expression
Copy_StringExpr(expression expr)
{
  expression e;
  char *string;
  
  assert (expression_tag(expr)==StringExpr_kind);
  if (!StringExpr_is_translated(expr)) TranslateStrings (expr);
  
  e = Create_StringExpr (expression_src_info(expr), NULL);
  StringExpr_is_translated (e) = TRUE;
  StringExpr_size (e) = StringExpr_size(expr);
  StringExpr_wide (e) = StringExpr_wide(expr);

  if (!StringExpr_wide (e)) {
    string = fe_strndup (HEAD(StringExpr_s(expr)), StringExpr_size (e));
  } else {
    string = fe_strndup (HEAD(StringExpr_s(expr)),
                         StringExpr_size (e)*sizeof(wchar_t));
  }
  StringExpr_s(e) = Create_List_String(string, NULL);
  return e;
}

/* ----------------------------------------------------------------------------
 * Print the (translated) string in EXPR
 */
void
Print_StringExpr(expression expr, char *quote)
{
  String s;
  
  if(expression_tag(expr)!=StringExpr_kind) return;
  assert (List_String_next(StringExpr_s(expr))==NULL);
  
  s = List_String_data(StringExpr_s(expr));
  if(!StringExpr_wide(expr)) {
    print_quoted_string(s, StringExpr_size(expr), quote);
  } else {
    print_wide_quoted_string(s, StringExpr_size(expr), quote);
  }
}

void
Print_CharExpr (CharExpr e, char *quote)
{
  String s;
  
  if(expression_tag(e)!=CharExpr_kind) return;
  
  s = CharExpr_c(e);
  print_quoted_string(s, 2, quote);
}

void
Print_WideCharExpr (WideCharExpr e, char *quote)
{
  String s;
  
  if(expression_tag(e)!=WideCharExpr_kind) return;
  
  s = WideCharExpr_c(e);
  print_wide_quoted_string(s, WideCharExpr_size(e)+1, quote);
}

/* ----------------------------------------------------------------------------
 * Traverse AST NODE and translate all strings
 */
void
ast_translate_strings(void *node,
                      void  (*trav_void) (void *me, Trav_void_table table))
{
  static struct s_void_cfront_TraverseTable translate_actions;
  static Bool done=FALSE;

  if(!done) {
    translate_actions.action_StringExpr[PRE_ACTION] = TranslateStrings;
    translate_actions.action_CharExpr[PRE_ACTION]   = translate_CharExpr;
    /* WideChar_kind is introduced by translate_CharExpr */
  }
  trav_void(node, &translate_actions);
}
